package cn.enited.devtools.apiaggregator.service.impl;

import cn.enited.devtools.apiaggregator.api.cache.ApiCacheManager;
import cn.enited.devtools.apiaggregator.api.cache.CacheSyncManager;
import cn.enited.devtools.apiaggregator.consts.ApiConst;
import cn.enited.devtools.apiaggregator.consts.CommonConst;
import cn.enited.devtools.apiaggregator.consts.ErrorCode;
import cn.enited.devtools.apiaggregator.consts.MatchRegexPolicy;
import cn.enited.devtools.apiaggregator.converter.ProjectConverter;
import cn.enited.devtools.apiaggregator.dto.ProjectDto;
import cn.enited.devtools.apiaggregator.entity.Project;
import cn.enited.devtools.apiaggregator.exception.BizException;
import cn.enited.devtools.apiaggregator.repo.ProjectRepository;
import cn.enited.devtools.apiaggregator.service.ProjectService;
import cn.enited.devtools.apiaggregator.utils.NumUtil;
import cn.enited.devtools.apiaggregator.utils.RedissonUtil;
import cn.enited.devtools.apiaggregator.vo.ProjectParamVo;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class ProjectServiceImpl extends AbstractService<ProjectRepository, Project> implements ProjectService {

    private ProjectRepository repo;

    private ProjectConverter converter;

    private ApiCacheManager cacheManager;

    private CacheSyncManager cacheSyncManager;

    private RedissonUtil redissonUtil;

    @Autowired
    public void setRepo(ProjectRepository repo) {
        this.repo = repo;
    }

    @Override
    public ProjectRepository getRepo() {
        return this.repo;
    }

    @Autowired
    public void setConverter(ProjectConverter converter) {
        this.converter = converter;
    }

    @Autowired
    public void setCacheManager(ApiCacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Autowired
    public void setCacheSyncManager(CacheSyncManager cacheSyncManager) {
        this.cacheSyncManager = cacheSyncManager;
    }

    @Autowired
    public void setRedissonUtil(RedissonUtil redissonUtil) {
        this.redissonUtil = redissonUtil;
    }

    @Override
    public ProjectDto get(Long bizId) {
        return repo.getByBizId(bizId).map(converter::entity2Dto)
                .orElseThrow(() -> new BizException(ErrorCode.NOT_EXIST.code(), "项目不存在"));
    }

    @Override
    public ProjectDto get(String name) {
        Query query = Query.query(Criteria.where("name").is(name).and(CommonConst.ENTITY.DEL_FLAG).is(false));
        return repo.findOne(query).map(converter::entity2Dto)
                .orElseThrow(() -> new BizException(ErrorCode.NOT_EXIST.code(), "项目不存在"));
    }

    @Override
    public List<ProjectDto> findAll(ProjectParamVo param) {
        Query query = buildFindQuery(param);
        List<Project> entities = repo.findAll(query);
        return converter.entities2DtoList(entities);
    }

    @Override
    public Page<ProjectDto> findPage(ProjectParamVo param) {
        Query query = buildFindQuery(param);
        Page<Project> page = repo.findPage(query, param.getOneBasedPageNumber(), param.getPageSize());
        return convertEntityPageToDtoPage(page, () -> converter.entities2DtoList(page.getContent()));
    }

    @Override
    public List<String> getAllProjectNames() {
        return repo.getAllProjectNames();
    }

    private Query buildFindQuery(ProjectParamVo param) {
        return buildQuery(param, p -> {
            List<Criteria> criteriaList = new ArrayList<>();
            criteriaList.add(Criteria.where(CommonConst.ENTITY.DEL_FLAG).is(false));
            if (NumUtil.isValidId(param.getBizId())) {
                criteriaList.add(Criteria.where(CommonConst.ENTITY.BIZ_ID).is(param.getBizId()));
            } else if (CollectionUtil.isNotEmpty(param.getBizIds())) {
                criteriaList.add(Criteria.where(CommonConst.ENTITY.BIZ_ID).in(param.getBizIds()));
            }
            if (StrUtil.isNotBlank(param.getName())) {
                criteriaList.add(Criteria.where("name").is(param.getName()));
            }
            if (StrUtil.isNotBlank(param.getDescription())) {
                criteriaList.add(Criteria.where("description").regex(buildMatchRegex(param.getDescription(), MatchRegexPolicy.ALL)));
            }
            Criteria criteria = new Criteria();
            criteria.andOperator(criteriaList);
            return criteria;
        });
    }

    @Override
    public Long add(ProjectDto project) {
        Project entity = converter.dto2Entity(project);
        try {
            repo.save(entity);
            cacheManager.putProjectPath(entity.getName(), CommonConst.API_ENV.PRODUCT, entity.getProductConfiguration());
            if (Objects.nonNull(entity.getTestConfiguration())) {
                cacheManager.putProjectPath(entity.getName(), CommonConst.API_ENV.TEST, entity.getProductConfiguration());
            }
            if (Objects.nonNull(entity.getTrainingConfiguration())) {
                cacheManager.putProjectPath(entity.getName(), CommonConst.API_ENV.TRAINING, entity.getTrainingConfiguration());
            }
            cacheSyncManager.publishCacheUpdateEvent(ApiConst.SYNC_TYPE.PROJECT_PATH, entity.getName());
        } catch (Exception e) {
            log.error("add project error:", e);
            repo.delete(entity.getBizId());
            cacheManager.removeProjectPath(entity.getName(), CommonConst.API_ENV.PRODUCT);
            if (Objects.nonNull(entity.getTestConfiguration())) {
                cacheManager.removeProjectPath(entity.getName(), CommonConst.API_ENV.TEST);
            }
            if (Objects.nonNull(entity.getTrainingConfiguration())) {
                cacheManager.removeProjectPath(entity.getName(), CommonConst.API_ENV.TRAINING);
            }
            if (e instanceof BizException) {
                throw e;
            } else {
                throw new BizException(ErrorCode.ERROR.code(), "添加项目失败");
            }
        }
        return entity.getBizId();
    }

    @Override
    public void update(ProjectDto project) {
        Project entity = converter.dto2Entity(project);
        RLock locker = redissonUtil.getLock(StrUtil.format("{}{}", CommonConst.REDIS.UPDATE_KEY_PREFIX, entity.getBizId()));
        try {
            locker.lock(500L, TimeUnit.MILLISECONDS);
            try {
                repo.update(entity);
            } catch (Exception e) {
                log.error("修改Project数据库数据时发生异常:", e);
                if (e instanceof BizException) {
                    throw e;
                } else {
                    throw new BizException(ErrorCode.ERROR.code(), "修改项目失败");
                }
            }
            cacheManager.putProjectPath(entity.getName(), CommonConst.API_ENV.PRODUCT, entity.getProductConfiguration());
            if (Objects.nonNull(entity.getTestConfiguration())) {
                cacheManager.putProjectPath(entity.getName(), CommonConst.API_ENV.TEST, entity.getProductConfiguration());
            }
            if (Objects.nonNull(entity.getTrainingConfiguration())) {
                cacheManager.putProjectPath(entity.getName(), CommonConst.API_ENV.TRAINING, entity.getTrainingConfiguration());
            }
            cacheSyncManager.publishCacheUpdateEvent(ApiConst.SYNC_TYPE.PROJECT_PATH, entity.getName());
        } finally {
            locker.unlock();
        }
    }

    @Override
    public void delete(Long bizId) {
        repo.getByBizId(bizId).ifPresent(entity -> {
            repo.delete(bizId);
            cacheManager.removeProjectPath(entity.getName(), CommonConst.API_ENV.PRODUCT);
            if (Objects.nonNull(entity.getTestConfiguration())) {
                cacheManager.removeProjectPath(entity.getName(), CommonConst.API_ENV.TEST);
            }
            if (Objects.nonNull(entity.getTrainingConfiguration())) {
                cacheManager.removeProjectPath(entity.getName(), CommonConst.API_ENV.TRAINING);
            }
            cacheManager.removeAllAnalysisObjectsOfProject(entity.getName());
        });
    }
}
